/*******************************************************************************
 * Copyright (c) 2006 CEA List.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     CEA List - initial API and implementation
 *******************************************************************************/
package com.cea.papyrus.diagram.statemachine.editpart;

import java.util.ArrayList;
import java.util.List;
import java.util.Vector;

import org.eclipse.draw2d.IFigure;
import org.eclipse.draw2d.PositionConstants;
import org.eclipse.draw2d.geometry.Dimension;
import org.eclipse.draw2d.geometry.Point;
import org.eclipse.draw2d.geometry.PrecisionRectangle;
import org.eclipse.draw2d.geometry.Rectangle;
import org.eclipse.gef.GraphicalEditPart;
import org.eclipse.gef.handles.ResizableHandleKit;
import org.eclipse.gef.requests.ChangeBoundsRequest;

import com.cea.papyrus.core.utils.Debug;
import com.cea.papyrus.diagram.statemachine.figure.RegionFigure;
import com.cea.papyrus.diagram.statemachine.helper.NodeRegionHelper.Zone;
import com.cea.papyrus.diagraminterchange2.di2.GraphNode;

/**
 * Provides for a specific ResizableEditPolicy based on:.
 * 
 * @see org.eclipse.gef.editpolicies.ResizableEditPolicy
 * Namely associate specific ResizableHandle which are shown only in the allowed resize direction.
 * Secondly provides a specific graphical feedback when resizing a region: neighbouring regions which
 * share the same border being moved will be included in the shadow figure even though they were not
 * included in the selection. Such neighbouring regions need to be resized this is why we show this particular
 * feedback and override the default behavior provided by GEF. Furthermore it is used to provide an updated
 * ghost figure when we hover over a region during a drag and drop
 * 
 * @author David Servat
 * @since 29 jan. 07
 */
public class ResizableEditPolicy extends org.eclipse.gef.editpolicies.ResizableEditPolicy{

	/**
	 * The channel.
	 */
	private int channel = 501;

	/** The associated request. */
	private ChangeBoundsRequest request;
	
	/** The target figure, i.e. the figure associated to the targetNode region . Used for ta change of coordinates. */
	private RegionFigure targetFig;
	
	/** Flag to indicate a change in the referent figure is needed. */
	private boolean changeReferentFigure = false;
	
	/**
	 * Provides a specific list of SelectionHandles.
	 * 
	 * @return a list of the Handles
	 */
	@Override
	protected List createSelectionHandles(){
		List list = new ArrayList();
		int dirs = getResizeDirections();
		//by default (case when dirs == -1 or 0)
		//no move nor resize handles are provided
		//this is to cope with the default region included in the statemachine
		//for which it would not make any sense	
		if(dirs >0){
			//resize handles are added depending on a bit-wise test of dirs
			//in any case we do not provide oblique resize handles
			if ((dirs & PositionConstants.EAST) != 0)
				ResizableHandleKit.addHandle((GraphicalEditPart)getHost(), list, PositionConstants.EAST);
			if ((dirs & PositionConstants.SOUTH) != 0)
				ResizableHandleKit.addHandle((GraphicalEditPart)getHost(), list, PositionConstants.SOUTH);
			if ((dirs & PositionConstants.WEST) != 0)
				ResizableHandleKit.addHandle((GraphicalEditPart)getHost(), list, PositionConstants.WEST);
			if ((dirs & PositionConstants.NORTH) != 0){
				ResizableHandleKit.addHandle((GraphicalEditPart)getHost(), list, PositionConstants.NORTH);
			}
		}
		return list;
	}

	/**
	 * This method is overriden here to save the request 
	 * and change the figure used to perform coordinate change.
	 * 
	 * @param request the Request
	 */ 
	@Override
	protected void showChangeBoundsFeedback(ChangeBoundsRequest request){
		this.request = (ChangeBoundsRequest)request;
		IFigure feedback = getDragSourceFeedbackFigure();
		
		PrecisionRectangle rect = new PrecisionRectangle(getInitialFeedbackBounds().getCopy());
		
		//if regions are part of different state machines or state
		if(changeReferentFigure)
			//use the target figure as referent for coordinate change
			targetFig.translateToAbsolute(rect);
		//both regions are part of the same state machine or state
		else
			//use default host figure
			getHostFigure().translateToAbsolute(rect);
		rect.translate(request.getMoveDelta());
		rect.resize(request.getSizeDelta());
		
		feedback.translateToRelative(rect);
		feedback.setBounds(rect);
	}
	
	/**
	 * This method is overriden to change the ghost figure shown when either resizing a region or moving it around.
	 * In the case of a resize we need to update the bounds of the ghost figure as the union of bounds of resized regions.
	 * This is done by a check of the neighbouring regions which share the same border to be moved by the resize request.
	 * In the case of a move we need to show what will happen to the region when droped on a given one: thus show a half-sized region
	 * located on one of the side of its parent region depending on the position of the mouse.
	 * In the course of this process we assign a corresponding drop location to the region.
	 * 
	 * @param a rectangle which is the bounds of the ghost figure to be shown
	 */ 
	@Override
	protected Rectangle getInitialFeedbackBounds(){
		Debug.debug(this, "call ResizableEditPolicy.getInitialFeedbackBounds()", channel);
		Dimension sizeDelta = request.getSizeDelta();
	
		//restore default flag value
		changeReferentFigure = false;
		
		//we test whether this is a move request or a resize request
		if((sizeDelta.width == 0) && (sizeDelta.height == 0)){
			//a move request on a region
			
			if(getHost() instanceof RegionEditPart){
				//we retrieve the RegionEditPart associated to the policy
				RegionEditPart regionEP = (RegionEditPart)getHost();
				//then retrieve the associated DragTracker
				RegionDragTracker tracker = regionEP.getRegionDragTracker();
				if(tracker != null){
					//then thanks to the tracker retrieve the target region, i.e. the region we are about to drop another on
					RegionEditPart targetEP = tracker.getTargetRegionEditPart();
					if(targetEP != null){
						//retrieve the target region figure 
						targetFig = (RegionFigure)targetEP.getFigure();
						//make a local copy
						Rectangle targetFigBounds = targetFig.getBounds().getCopy();
						//transform the coordinates to absolute
						targetFig.translateToAbsolute(targetFigBounds);
						//retrieve mouse location
						Point mouseLocation = request.getLocation();
						//but we set the move deltas to 0
						request.getMoveDelta().setLocation(0, 0);
						//because here what matters is the mouse location, but not the move between the previous mouse location and the next
						//this is irrelevant and furthermore if we do not set these to zero, the ghost figure will be moved around
						//which we want to prevent
	
						//we compute to values which account for the position of the mouse location w.r.t. to the diagonalsof the targetFig
						//d1 is for the first diagonal (going up)
						double d1 = mouseLocation.y-1.0*targetFigBounds.height/targetFigBounds.width*(targetFigBounds.x-mouseLocation.x)-targetFigBounds.y-targetFigBounds.height;
						//d2 is for the second (going down)
						double d2 = mouseLocation.y+1.0*targetFigBounds.height/targetFigBounds.width*(targetFigBounds.x-mouseLocation.x)-targetFigBounds.y; 
	
						//retrieve the target region node
						GraphNode targetNode = targetEP.getGraphNode();
						//make local copies of its position and size
						Point targetLocation = targetNode.getPosition().getCopy();
						Dimension targetSize = targetNode.getSize().getCopy();

						//and retrieve the associated graph node
						GraphNode regionNode = regionEP.getGraphNode();
						//and perform checks on (d1, d2)
						//based on them we perform corresponding change (scaling, translation) on targetFigBounds
						//and updates the graph node drop location property
						if((d1<=0) && (d2<=0)){
							//TOP
							targetSize.scale(1.0, 0.5);
							regionNode.getProperty("dropLocation").setValue(Zone.TOP);
						}
						else if((d1<=0) && (d2>0)){
							//LEFT
							targetSize.scale(0.5, 1.0);
							regionNode.getProperty("dropLocation").setValue(Zone.LEFT);
						}
						else if((d1>0) && (d2<=0)){
							//RIGHT
							targetSize.scale(0.5, 1.0);
							targetLocation.translate(targetSize.width+1, 0);
							regionNode.getProperty("dropLocation").setValue(Zone.RIGHT);
						}
						else if((d1>0) && (d2>0)){
							//BOTTOM
							targetSize.scale(1.0, 0.5);
							targetLocation.translate(1, targetSize.height);
							regionNode.getProperty("dropLocation").setValue(Zone.BOTTOM);
						}
						if(!regionNode.getSemanticParent().equals(targetNode.getSemanticParent()))
							changeReferentFigure = true;
						return new Rectangle(targetLocation, targetSize);
					}
				}
			}
			//if by any chance we had got here without a drag tracker or hovered over something else than a region
			//revert to the standard ghost figure
			return super.getInitialFeedbackBounds();
		}
		//this is a resize request
		else{
			//first retrieve the direction of resize
			int direction = request.getResizeDirection();
			//then the edit part
			//retrieve the edit part associated to the policy
			RegionEditPart regionEP = (RegionEditPart)getHost();
			//the region node associated
			GraphNode region = (GraphNode)regionEP.getModel();
			//a vector to get all the nodes impacted by the resize
			Vector<GraphNode> nodes = new Vector<GraphNode>();
			//test the direction and call the appropriate method
			if(direction == PositionConstants.NORTH)
				//retrieve the list of nodes that are at the BOTTOM of NORTH border
				nodes = Zone.getBottomNeighboursOfRegionTopBorder(region);
			else if(direction == PositionConstants.SOUTH)
				//retrieve the list of nodes that are at the TOP of SOUTH border
				nodes = Zone.getTopNeighboursOfRegionBottomBorder(region);
			else if(direction == PositionConstants.EAST)
				//retrieve the list of nodes that are at the LEFT of EAST border
				nodes = Zone.getLeftNeighboursOfRegionRightBorder(region);
			else if(direction == PositionConstants.WEST)
				//retrieve the list of nodes that are at the RIGHT of WEST border
				nodes = Zone.getRightNeighboursOfRegionLeftBorder(region);
			//now we compute the union of the region node bounds
			Rectangle rect = null;
			for(int i=0; i<nodes.size(); i++){
				//exclude the splitter nodes
				if(nodes.get(i).getSemanticModel() != null){
					Rectangle currentBounds = new Rectangle(nodes.get(i).getPosition().getCopy(), nodes.get(i).getSize().getCopy());
					if(rect == null)
						rect = currentBounds;
					else
						rect = rect.union(currentBounds);
				}
			}
			return rect;
		}
	}
}
